package core;

import java.awt.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * 十六进制（简写为hex或下标16）在数学中是一种逢16进1的进位制，一般用数字0到9和字母A到F表示（其中:A~F即10~15）。
 * 例如十进制数57，在二进制写作111001，在16进制写作39。
 * 像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20
 */
public class HexUtil {

    /**
     * 用于建立十六进制字符的输出索引
     */
    private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    // ---------------------------------------------------------------------------------------------------- encode
    /**
     * 用于建立十六进制字符的输出索引
     */
    private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * 判断给定字符串是否为16进制数
     * 如果是，需要使用对应数字类型对象的{@code decode}方法解码
     * 例如：{@code Integer.decode}方法解码int类型的16进制数字
     *
     * @param value 值
     * @return 是否为16进制
     */
    public static boolean isHexNumber(String value) {
        if (StringUtil.isEmpty(value) || value.startsWith("-")) {
            // 负数
            return false;
        }
        int index = 0;
        if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
            index += 2;
        } else if (value.startsWith("#", index)) {
            index++;
        }
        try {
            new BigInteger(value.substring(index), 16);
        } catch (final NumberFormatException e) {
            return false;
        }
        return true;
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data byte数组
     * @return 十六进制char数组
     */
    public static char[] encodeHex(byte[] data) {
        return encodeHex(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data        byte数组
     * @param toLowerCase {@code true} 传换成小写格式 ， {@code false} 传换成大写格式
     * @return 十六进制char数组
     */
    public static char[] encodeHex(byte[] data, boolean toLowerCase) {
        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data     byte数组
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制char数组
     */
    protected static char[] encodeHex(byte[] data, char[] toDigits) {
        final int l = data.length;
        final char[] out = new char[l << 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
        return out;
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data byte数组
     * @return 十六进制String
     */
    public static String encodeHexStr(byte[] data) {
        return encodeHexStr(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data        byte数组
     * @param toLowerCase {@code true} 传换成小写格式 ， {@code false} 传换成大写格式
     * @return 十六进制String
     */
    public static String encodeHexStr(byte[] data, boolean toLowerCase) {
        return new String(encodeHex(data, toLowerCase));
    }

    /**
     * 将十六进制字符数组转换为字节数组
     *
     * @param hexChars 十六进制char数组
     * @return byte数组
     * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度，将抛出运行时异常
     */
    public static byte[] decodeHex(char[] hexChars) throws RuntimeException {
        final int len = hexChars.length;

        if ((len & 0x01) != 0) {
            throw new RuntimeException("Odd number of characters.");
        }

        final byte[] out = new byte[len >> 1];

        // two characters form the hex value.
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(hexChars[j], j) << 4;
            j++;
            f = f | toDigit(hexChars[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }

    // ---------------------------------------------------------------------------------------------------- Color

    /**
     * 将十六进制字符串转换为字节数组
     *
     * @param hexStr 十六进制String
     * @return byte数组
     */
    public static byte[] decodeHex(String hexStr) {
        if (ObjectUtil.isEmpty(hexStr)) {
            return null;
        }
        return decodeHex(hexStr.toCharArray());
    }

    /**
     * 将十六进制字符转换成一个整数
     *
     * @param ch    十六进制char
     * @param index 十六进制字符在字符数组中的位置
     * @return 一个整数
     * @throws RuntimeException 当ch不是一个合法的十六进制字符时，抛出运行时异常
     */
    protected static int toDigit(char ch, int index) throws RuntimeException {
        final int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index);
        }
        return digit;
    }

    // ---------------------------------------------------------------------------------------------------- BigInteger

    /**
     * 将颜色值转换成十六进制字符串，格式为 #RRGGBB
     *
     * @param color 颜色值
     * @return 十六进制字符串
     */
    public static String toHex(Color color) {
        if (null == color) {
            return null;
        }
        return "#" + encodeHexStr(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()}, false);
    }

    /**
     * 将十六进制颜色值转换为Color对象，支持以下格式：
     * <pre>
     *   #FCFCFC
     *   FCFCFC
     *   #FCFC
     *   FCFC
     *   #333
     *   333
     * </pre>
     *
     * @param hexColor 十六进制颜色值
     * @return Color对象
     */
    public static Color toColor(String hexColor) {
        if (ObjectUtil.isEmpty(hexColor)) {
            return null;
        }

        // 去掉前缀
        if (hexColor.startsWith("#")) {
            hexColor = hexColor.substring(1);
        }

        int r, g, b;
        switch (hexColor.length()) {
            case 3: // #RGB
                r = Integer.parseInt(String.valueOf(hexColor.charAt(0)), 16) * 17;
                g = Integer.parseInt(String.valueOf(hexColor.charAt(1)), 16) * 17;
                b = Integer.parseInt(String.valueOf(hexColor.charAt(2)), 16) * 17;
                break;
            case 6: // #RRGGBB
                r = Integer.parseInt(hexColor.substring(0, 2), 16);
                g = Integer.parseInt(hexColor.substring(2, 4), 16);
                b = Integer.parseInt(hexColor.substring(4, 6), 16);
                break;
            default:
                throw new IllegalArgumentException("Invalid hex color: " + hexColor);
        }

        return new Color(r, g, b);
    }

    // ---------------------------------------------------------------------------------------------------- Charset

    /**
     * 将BigInteger转为16进制字符串
     *
     * @param bigInteger BigInteger
     * @return 16进制字符串
     */
    public static String toHex(BigInteger bigInteger) {
        if (null == bigInteger) {
            return null;
        }
        return bigInteger.toString(16);
    }

    /**
     * 将16进制字符串转为BigInteger
     *
     * @param hexStr 16进制字符串
     * @return BigInteger
     */
    public static BigInteger toBigInteger(String hexStr) {
        if (ObjectUtil.isEmpty(hexStr)) {
            return null;
        }
        return new BigInteger(hexStr, 16);
    }

    /**
     * 使用十六进制字符串解码为字符串，默认编码UTF-8
     *
     * @param hexStr 十六进制字符串
     * @return 解码后的字符串
     */
    public static String decodeHexStr(String hexStr) {
        return decodeHexStr(hexStr, StandardCharsets.UTF_8);
    }

    /**
     * 使用十六进制字符串解码为字符串
     *
     * @param hexStr  十六进制字符串
     * @param charset 编码
     * @return 解码后的字符串
     */
    public static String decodeHexStr(String hexStr, Charset charset) {
        if (ObjectUtil.isEmpty(hexStr)) {
            return hexStr;
        }
        return StringUtil.str(decodeHex(hexStr), charset);
    }

    // ---------------------------------------------------------------------------------------------------- Private methods

    /**
     * 字符串编码为16进制字符串，默认编码UTF-8
     *
     * @param str 字符串
     * @return 16进制字符串
     */
    public static String encodeHexStr(String str) {
        return encodeHexStr(str, StandardCharsets.UTF_8);
    }

    /**
     * 字符串编码为16进制字符串
     *
     * @param str     字符串
     * @param charset 编码
     * @return 16进制字符串
     */
    public static String encodeHexStr(String str, Charset charset) {
        if (ObjectUtil.isEmpty(str)) {
            return str;
        }
        return encodeHexStr(StringUtil.bytes(str, charset));
    }
}